Skip to content

Optimize managed postprocessing and allocations - #5

Closed
ericstj wants to merge 1 commit into
mainfrom
bump-0.1.1
Closed

Optimize managed postprocessing and allocations#5
ericstj wants to merge 1 commit into
mainfrom
bump-0.1.1

Conversation

@ericstj

@ericstj ericstj commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Summary

  • decode raw model logits instead of computing log-softmax
  • use sparse Viterbi predecessor tables and pooled compact backpointers
  • pool inference input and score buffers and avoid copying dense ONNX outputs
  • add randomized decoder parity coverage and managed/end-to-end benchmarks

Results

  • sparse Viterbi is 54% faster with 97% less managed allocation
  • end-to-end managed allocation falls 52% for short text and 66% for long text
  • model-dominated end-to-end latency remains effectively unchanged

Testing

  • dotnet test PrivacyFilter.Net.sln -c Release --nologo

Decode raw model logits, use sparse Viterbi transitions, and pool temporary inference and backpointer buffers. Add parity coverage and before/after benchmarks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a9621082-f9c8-4ec8-ac3c-faaf34eb7d0f
Copilot AI review requested due to automatic review settings July 23, 2026 07:00
@ericstj

ericstj commented Jul 23, 2026

Copy link
Copy Markdown
Owner Author

Superseded by the same commit on the dedicated optimization branch.

@ericstj ericstj closed this Jul 23, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR optimizes the managed post-processing path of PrivacyFilterNet inference by eliminating log-softmax from production decoding, introducing a sparse Viterbi predecessor representation with pooled backpointers, and pooling/copy-avoiding inference buffers to reduce allocations while preserving decode outputs.

Changes:

  • Decode using raw logits (no production log-softmax) and add parity tests to ensure ArgMax/Viterbi invariance.
  • Replace dense Viterbi transition matrix + int backpointers with sparse predecessor tables and pooled compact (byte) backpointers.
  • Pool inference input/score buffers and avoid copying dense ONNX outputs when possible; expand benchmark coverage and document results.
Show a summary per file
File Description
tests/PrivacyFilter.Net.Tests/DecoderTests.cs Adds randomized parity tests (logits vs log-softmax) and a dense reference decoder to validate sparse Viterbi correctness.
src/PrivacyFilter.Net/ViterbiDecoder.cs Implements sparse predecessor tables and pooled compact backpointers; updates decoder API to use ReadOnlySpan<float>.
src/PrivacyFilter.Net/PrivacyFilter.Net.csproj Exposes internals to the benchmarks project to enable benchmark/test access to internal helpers.
src/PrivacyFilter.Net/PrivacyFilter.cs Pools inference buffers, avoids copying dense logits outputs, and decodes directly from logits using span-based APIs.
bench/results.md Documents benchmark results for the managed postprocessing optimizations.
bench/PrivacyFilter.Net.Benchmarks/Program.cs Adds managed postprocessing microbenchmarks and switches to BenchmarkSwitcher for multiple benchmark classes.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comments suppressed due to low confidence (1)

src/PrivacyFilter.Net/PrivacyFilter.cs:208

  • Returning pooled ids/mask arrays with clearArray: true clears the entire rented buffers (often larger than windowLength), which can add avoidable overhead per inference window. Consider clearing only the used prefix and returning with clearArray: false (or add a comment if full-buffer clearing is a deliberate security choice).
            finally
            {
                ArrayPool<long>.Shared.Return(ids, clearArray: true);
                ArrayPool<long>.Shared.Return(mask, clearArray: true);
            }
  • Files reviewed: 6/6 changed files
  • Comments generated: 2
  • Review effort level: Low

Comment on lines +117 to +120
finally
{
ArrayPool<float>.Shared.Return(scoreBuffer, clearArray: true);
}
Comment on lines +170 to 176
finally
{
lastLabel = backpointers[(token * classCount) + lastLabel];
path[token] = lastLabel;
if (backpointerBuffer is not null)
{
ArrayPool<byte>.Shared.Return(backpointerBuffer, clearArray: true);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants